# -*- coding: utf-8 -*-

import os
import sys
from collections import OrderedDict

from bopress.cache import Cache
from bopress.log import Logger
from bopress.utils import Utils

__author__ = 'yezang'


class Plugin(object):
    def __init__(self):
        # 插件名称
        self.name = ""
        # 插件名称
        self.theme = False
        # 插件入口模块(包含包名)全称
        self.module_name = ""
        # 插件发布网站
        self.uri = ""
        # 插件描述
        self.description = ""
        # 插件作者
        self.author = ""
        # 插件作者主页
        self.author_uri = ""
        # 插件版本
        self.version = "1.0"
        # 是否永久加载、如果为``True``则不允许在后台执行激活或者卸载动作
        self.alway = False

    def dict(self, dict_=None, order=False):
        if dict_:
            self.name = dict_.get("name", "")
            self.theme = dict_.get("theme", False)
            self.module_name = dict_.get("module_name", "")
            self.uri = dict_.get("uri", "")
            self.description = dict_.get("description", "")
            self.author = dict_.get("author", "")
            self.author_uri = dict_.get("author_uri", "")
            self.version = dict_.get("version", "1.0")
            self.alway = dict_.get("alway", False)
        else:
            if not order:
                dict_ = dict()
            else:
                dict_ = OrderedDict()
            dict_["name"] = self.name
            dict_["module_name"] = self.module_name
            dict_["theme"] = self.theme
            dict_["uri"] = self.uri
            dict_["description"] = self.description
            dict_["author"] = self.author
            dict_["author_uri"] = self.author_uri
            dict_["version"] = self.version
            dict_["alway"] = self.alway
            return dict_


class Hooks(object):
    _actions = dict()
    _filters = dict()
    _files = set()
    _plugins = list()
    _admin_menus = OrderedDict()
    _static_paths = dict()
    _ui_list_tables = dict()

    @staticmethod
    def load(path, callback=None):
        Hooks._admin_menus.clear()
        Hooks._filters.clear()
        Hooks._actions.clear()
        Hooks._plugins = []
        Hooks._static_paths.clear()
        Hooks._files.clear()
        # 在加载用户插件之前执行系统插件
        if callback:
            callback()
        # 插件校验，移除不存在的插件
        Hooks._valid_plugins(path)
        valid_plugins = Cache.data().get(["bo_plugins"], dict())
        for f in valid_plugins:
            # 读取meta.json
            _plugin = Plugin()
            _plugin.dict(Utils.decode(Utils.text_read(f)))
            # 插件名称，入口模块名，版本是必须的
            if _plugin.name and _plugin.module_name and _plugin.version:
                # 是否永久加载
                if _plugin.alway:
                    if _plugin.module_name in sys.modules:
                        del sys.modules[_plugin.module_name]
                    __import__(_plugin.module_name)
                else:
                    # 只加载激活了的插件
                    if valid_plugins[f]:
                        if _plugin.module_name in sys.modules:
                            del sys.modules[_plugin.module_name]
                        __import__(_plugin.module_name)
                p_dict = _plugin.dict()
                p_dict['status'] = False
                p_dict['path'] = f
                Hooks._plugins.append(p_dict)

        for ak in Hooks._actions:
            v = Hooks._actions[ak]
            Hooks._actions[ak] = sorted(v, key=lambda s: s['priority'])

        for ak in Hooks._filters:
            v = Hooks._filters[ak]
            Hooks._filters[ak] = sorted(v, key=lambda s: s['priority'])

    @staticmethod
    def ui_list_tables():
        return Hooks._ui_list_tables

    @staticmethod
    def static_paths():
        return Hooks._static_paths

    @staticmethod
    def actions():
        return Hooks._actions

    @staticmethod
    def filters():
        return Hooks._filters

    @staticmethod
    def plugins():
        return Hooks._plugins

    @staticmethod
    def admin_menus():
        return Hooks._admin_menus

    @staticmethod
    def _get_attr(o, name, default=""):
        try:
            return getattr(o, name)
        except AttributeError as e:
            Logger.debug(str(e))
            return default

    @staticmethod
    def _valid_plugins(path):
        Hooks._scan(path)

        # 清除已经被移除的插件
        valid_plugins = Cache.data().get(["bo_plugins"], dict())
        valids = set([i for i in valid_plugins.keys()])
        diff = valids.difference(Hooks._files)
        for file_full_name in diff:
            del valid_plugins[file_full_name]
        for a in Hooks._files:
            if a not in valid_plugins:
                valid_plugins[a] = False

        Cache.data().set(["bo_plugins"], valid_plugins, 0)

    @staticmethod
    def _scan(path):
        try:
            # for python 3.5+
            files = os.scandir(path)
            for f in files:
                if f.is_dir():
                    Hooks._scan(f.path)
                else:
                    if f.name == "meta.json":
                        Hooks._files.add(f.path)
        except Exception as e:
            Logger.debug(str(e))
            for parent, dir_names, file_names in os.walk(path):
                for file_name in file_names:
                    if file_name == "meta.json":
                        file_full_name = os.path.join(parent, file_name)
                        Hooks._files.add(file_full_name)


class AdminMenus(object):
    @staticmethod
    def get_menu(menu_slug):
        return Hooks.admin_menus().get(menu_slug, None)

    @staticmethod
    def do_admin_menus(handler, current_menu_item):
        menus_ = OrderedDict()
        # 对菜单排序、得到没有父菜单的第一级菜单、并计算好每个菜单的层级关系
        for item in sorted(Hooks.admin_menus().values(), key=lambda s: s["position"]):
            parent = Hooks.admin_menus().get(item["parent"])
            if parent:
                # 计算每个菜单的层级关系
                item["path"] = parent["path"] + [item["parent"]]
                parent["has_children"] = True
            if not item["parent"]:
                menus_[item["menu_slug"]] = item
        htmls = list()
        AdminMenus._list_menus(menus_, htmls, handler, current_menu_item)
        return "".join(htmls)

    @staticmethod
    def _list_menus(menus, htmls=list(), handler=None, current_menu_item=None):
        for k in menus.keys():
            menu_item = menus.get(k)
            if not menu_item:
                continue
            if menu_item["place"] != 'menu':
                continue
            caps = menu_item["capability"]
            if not handler.auth(set(caps), False, False):
                continue
            href = "%s?page=%s" % (handler.reverse_url("admin"), menu_item["menu_slug"])
            has_children = menu_item["has_children"]
            if has_children:
                css_cls = ""
                if k in current_menu_item["path"]:
                    css_cls = "active"
                htmls.append('<li class="treeview %s">' % css_cls)
                htmls.append("""
                                    <a href="%s">
                                        <i class="fa fa-square-o"></i> <span>%s</span>
                                        <span class="pull-right-container">
                                          <i class="fa fa-angle-left pull-right"></i>
                                        </span>
                                      </a>
                                """ % ("#", menu_item["menu_title"]))
                menus_ = OrderedDict()
                for item in Hooks.admin_menus().values():
                    if item["parent"] == menu_item["menu_slug"]:
                        menus_[item["menu_slug"]] = item
                if not css_cls:
                    htmls.append('<ul class="treeview-menu">')
                else:
                    htmls.append('<ul class="treeview-menu menu-open">')
                AdminMenus._list_sub_menus(menus_, htmls, handler, current_menu_item)
                htmls.append('</ul>')
                htmls.append('</li>')
            else:
                css_cls = ""
                if k == current_menu_item["menu_slug"]:
                    css_cls = "active"
                htmls.append("""
                    <li class="%s"><a href="%s"><i class="fa fa-circle-o"></i> <span>%s<span></a></li>
                """ % (css_cls, href, menu_item["menu_title"]))

    @staticmethod
    def _list_sub_menus(menus, htmls=list(), handler=None, current_menu_item=None):
        items = sorted(menus.values(), key=lambda s: s["position"])
        for menu_item in items:
            if not menu_item:
                continue
            if menu_item["place"] != 'menu':
                continue
            caps = menu_item["capability"]
            if not handler.auth(set(caps), False, False):
                continue
            k = menu_item["menu_slug"]
            href = "%s?page=%s" % (handler.reverse_url("admin"), menu_item["menu_slug"])
            has_children = menu_item["has_children"]
            if has_children:
                css_cls = ""
                if k in current_menu_item["path"]:
                    css_cls = "active"
                htmls.append('<li class="%s">' % css_cls)
                htmls.append("""
                        <a href="%s">
                            <i class="fa fa-square-o"></i> <span>%s</span>
                            <span class="pull-right-container">
                              <i class="fa fa-angle-left pull-right"></i>
                            </span>
                          </a>
                    """ % ("#", menu_item["menu_title"]))
                menus_ = OrderedDict()
                for item in Hooks.admin_menus().values():
                    if item["parent"] == menu_item["menu_slug"]:
                        menus_[item["menu_slug"]] = item
                if not css_cls:
                    htmls.append('<ul class="treeview-menu">')
                else:
                    htmls.append('<ul class="treeview-menu menu-open">')
                AdminMenus._list_sub_menus(menus_, htmls, handler, current_menu_item)
                htmls.append('</ul>')
                htmls.append('</li>')
            else:
                css_cls = ""
                if k == current_menu_item["menu_slug"]:
                    css_cls = "active"
                htmls.append("""
                        <li class="%s"><a href="%s"><i class="fa fa-circle-o"></i> <span>%s<span></a></li>
                    """ % (css_cls, href, menu_item["menu_title"]))


def add_static_path(path):
    """
    添加插件静态文件路径，相对路径
    比如plugins/a/b/c, 则此路径为 /a/b/c or a/b/c..
    :param path: 相对路径
    :return: None
    """
    if not path:
        return
    c = path.split("/")
    new_path = "/".join([x for x in c if x != ''])
    Hooks.static_paths()[new_path] = Utils.md5(new_path)


def add_action(name="", func=None, priority=10):
    """
    在某一个扩展点执行一些动作
    :param name: 扩展点名称
    :param func: 自定义函数
    :param priority:
    :return:
    """
    if not name:
        return
    action = dict()
    action["func"] = func
    action["priority"] = priority
    if name.startswith("bo_api_"):
        b = name.split("_")
        if len(b) > 3:
            action["api_action"] = "_".join(b[3:])
            name = "_".join(b[0:3])
        else:
            return
    if name in Hooks.actions():
        Hooks.actions()[name].append(action)
    else:
        Hooks.actions()[name] = [action]


def do_action(name="", *args, **kwargs):
    """
    安置及执行扩展点，供其它模块扩展
    :param name: 扩展点名称
    :param args:
    :param kwargs:
    :return:
    """
    if not name:
        return None
    if name in Hooks.actions().keys():
        arr = Hooks.actions()[name]
        for item in arr:
            if "api_action" in item:
                api_action = item["api_action"]
                if args[1] == api_action:
                    item["func"](args[0])
            else:
                item["func"](*args, **kwargs)


def add_filter(name="", func=None, priority=10):
    """
    过滤器
    在某一扩展点对一些值或内容进行修改
    :param name:扩展点名称
    :param func: 自定义函数，函数的第一个参数默认为修改的内容
    :param priority:
    :return:
    """
    if not name:
        return
    action = dict()
    action["func"] = func
    action["priority"] = priority
    if name in Hooks.filters():
        Hooks.filters()[name].append(action)
    else:
        Hooks.filters()[name] = [action]


def apply_filters(name="", v=None, *args, **kwargs):
    """
    安置及执行过滤器
    :param name:扩展点名称
    :param v: 可以过滤的值
    :param args:
    :param kwargs:
    :return:
    """
    if name in Hooks.filters():
        arr = Hooks.filters()[name]
        for item in arr:
            v = item["func"](v, *args, **kwargs)
    return v


def add_menu_page(page_title, menu_title, menu_slug='',
                  capability=list(), cb=None, icon_url='', position=10, place="menu"):
    """
    添加菜单
    :param page_title: 页标题
    :param menu_title: 菜单标题
    :param menu_slug: 菜单ID
    :param capability: 访问权限
    :param cb: str or func
    :param icon_url: 菜单图标
    :param position: 菜单位置
    :param place: default is `menu`, other is `single`, `quick`
    """
    menu = OrderedDict()
    menu["page_title"] = page_title
    menu["menu_title"] = menu_title
    menu["capability"] = capability
    menu["menu_slug"] = menu_slug
    menu["cb"] = cb
    menu["parent"] = ""
    menu["path"] = list()
    menu["icon_url"] = icon_url
    menu["position"] = position
    menu["has_children"] = False
    menu["place"] = place
    Hooks.admin_menus()[menu_slug] = menu


def add_submenu_page(parent_slug, page_title, menu_title, menu_slug='', capability=list(),
                     cb=None, icon_url='', position=10, place="menu"):
    """
    添加子菜单
    :param parent_slug: 父菜单ID
    :param page_title: 页标题
    :param menu_title: 菜单标题
    :param menu_slug: 菜单ID
    :param capability: 访问权限
    :param cb: str or func
    :param icon_url: 菜单图标
    :param position: 菜单图标
    :param place:  default is `menu`, other is `single`, `quick`
    :return:
    """
    menu = OrderedDict()
    menu["page_title"] = page_title
    menu["menu_title"] = menu_title
    menu["capability"] = capability
    menu["menu_slug"] = menu_slug
    menu["cb"] = cb
    menu["icon_url"] = icon_url
    menu["position"] = position
    menu["parent"] = parent_slug
    menu["has_children"] = False
    menu["place"] = place
    Hooks.admin_menus()[menu_slug] = menu


def _get_submenus_for_remove(menu_slug, menus):
    menus.add(menu_slug)
    for menu in Hooks.admin_menus().values():
        if menu["parent"] == menu_slug:
            menus.add(menu["menu_slug"])
            _get_submenus_for_remove(menu["menu_slug"], menus)


def remove_menus(menu_slugs):
    """
    Recursive remove menus.
    :param menu_slugs: list or tulpe or set.
    :return: boolean
    """
    if not menu_slugs:
        return False
    all_menus = set()
    for slug in menu_slugs:
        if slug in Hooks.admin_menus():
            # find it all children
            _get_submenus_for_remove(slug, all_menus)
            pass
    return True


def add_option_page(page_title, menu_title, menu_slug, capability=None, icon_url=""):
    """
    添加选项页菜单
    :param page_title: 页标题
    :param menu_title: 菜单标题
    :param menu_slug: 菜单ID
    :param capability: 访问权限
    :param icon_url: icon
    """
    if not capability:
        capability = ["manager_option"]
    add_submenu_page("bo-options-general", page_title, menu_title, menu_slug,
                     capability, "bocore/admin/options-general.html", icon_url)


class WebResources(object):
    _resources = None

    def __init__(self):
        self._resources = dict()

    def enqueue_script(self, handle="", src="", deps=list()):
        self._resources[handle + src] = (handle, src, deps, "base")

    def enqueue_style(self, handle="", src="", deps=list(), media=""):
        self._resources[handle + src] = (handle, src, deps, media, "base")

    def enqueue_plugin_script(self, handle="", src="", deps=list(), ver=""):
        self._resources[handle + src] = (handle, src, deps, ver, "plugin")

    def enqueue_plugin_style(self, handle="", src="", deps=list(), media="", ver=""):
        self._resources[handle + src] = (handle, src, deps, media, ver, "plugin")

    def to_html(self, resource_type="scripts", handler=None):
        if not handler:
            return ""
        htmls = list()
        if resource_type == "scripts":
            for k in self._resources:
                v = self._resources[k]
                t = v[-1]
                if t == "base":
                    script = """
                    <script type="text/javascript" src="{0}"></script>
                    """.format(handler.static_url(v[1]))
                    htmls.append(script)
                elif t == "plugin":
                    script = """
                    <script type="text/javascript" src="{0}"></script>
                    """.format(handler.plugin_static_url(v[1], v[-2]))
                    htmls.append(script)
        elif resource_type == "styles":
            for k in self._resources:
                v = self._resources[k]
                t = v[-1]
                if t == "base":
                    style = """
                    <link rel="stylesheet" href="{0}" type="text/css">
                    """.format(handler.static_url(v[1]))
                    htmls.append(style)
                elif t == "plugin":
                    style = """
                    <link rel="stylesheet" href="{0}" type="text/css">
                    """.format(handler.plugin_static_url(v[1], v[-2]))
                    htmls.append(style)
        return "".join(htmls)
